package com.ideaaedi.extspringcache.manager;


import com.ideaaedi.extspringcache.annotation.ExtCacheableOop;
import com.ideaaedi.extspringcache.enums.ExtCacheTypeEnum;
import com.ideaaedi.extspringcache.properties.ExtSpringCacheProperties;
import com.ideaaedi.extspringcache.provider.CacheManagerProvider;
import com.ideaaedi.extspringcache.provider.ExtCaffeineCacheManagerProvider;
import com.ideaaedi.extspringcache.provider.ExtRedisCacheManagerProvider;
import com.ideaaedi.extspringcache.provider.ExtRedisCaffeineCacheManagerProvider;
import com.ideaaedi.extspringcache.support.ExtCacheHelper;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;

import java.util.Collection;
import java.util.Collections;

import static com.ideaaedi.extspringcache.support.ExtCacheHelper.LOG_PREFIX;

/**
 * 自定义缓存管理器
 *
 * @author JustryDeng
 * @since 2020/11/4 12:56:08
 */
@SuppressWarnings({"NullableProblems"})
@Slf4j
public class ExtCacheManager implements CacheManager, SmartInitializingSingleton {
    
    public static final String BEAN_NAME = "extCacheManager";
    
    @Resource
    private ApplicationContext applicationContext;
    
    @Resource
    private ExtSpringCacheProperties extSpringCacheProperties;
    
    @Getter
    @Resource(name = ExtRedisCacheManagerProvider.BEAN_NAME)
    private CacheManagerProvider extRedisCacheManagerProvider;
    
    @Getter
    @Resource(name = ExtCaffeineCacheManagerProvider.BEAN_NAME)
    private CacheManagerProvider extCaffeineCacheManagerProvider;
    
    @Getter
    @Resource(name = ExtRedisCaffeineCacheManagerProvider.BEAN_NAME)
    private CacheManagerProvider extRedisCaffeineCacheManagerProvider;
    
    private CacheManager defaultCacheManager = null;
    
    @Override
    public Cache getCache(String name) {
        ExtCacheableOop oop = ExtCacheHelper.determineExtCacheOop(name);
        // 获取对应的CacheManager
        CacheManager cacheManager = pivot(oop);
        // 如果为空的话， 是否使用默认的cacheManager
        if (cacheManager == null && extSpringCacheProperties.isUseDefaultCacheManagerIfMiss()) {
            cacheManager = defaultCacheManager;
        }
        if (cacheManager == null) {
            return null;
        }
        return cacheManager.getCache(name);
    }
    
    @Override
    public Collection<String> getCacheNames() {
        ExtCacheableOop oop = ExtCacheHelper.determineExtCacheOop();
        // 获取对应的CacheManager
        CacheManager cacheManager = pivot(oop);
        // 如果为空的话， 是否使用默认的cacheManager
        if (cacheManager == null && extSpringCacheProperties.isUseDefaultCacheManagerIfMiss()) {
            cacheManager = defaultCacheManager;
        }
        if (cacheManager == null) {
            return Collections.emptyList();
        }
        return cacheManager.getCacheNames();
    }
    
    @Override
    public void afterSingletonsInstantiated() {
        // 初始化defaultCacheManager
        CacheManager cacheManager = applicationContext.getBean(CacheManager.class);
        if (cacheManager instanceof ExtCacheManager) {
            log.warn(LOG_PREFIX + " Cannot find other cacheManager except ExtCacheManager. Maybe some functions won’t work, such as @Cacheable annotation.");
            return;
        }
        log.info(LOG_PREFIX + " found defaultCacheManager [{}]", cacheManager);
        defaultCacheManager = cacheManager;
    }
    
    /**
     * 根据当前注解信息对象，路由获取对应的CacheManager
     *
     * @param oop
     *         当前注解信息对象
     *
     * @return oop对应的CacheManager
     */
    private CacheManager pivot(ExtCacheableOop oop) {
        if (oop == null) {
            return null;
        }
        CacheManager cacheManager;
        ExtCacheTypeEnum extCacheTypeEnum = ExtCacheTypeEnum.parseCacheType(oop);
        cacheManager = switch (extCacheTypeEnum) {
            case REDIS -> extRedisCacheManagerProvider.provide(oop);
            case CAFFEINE -> extCaffeineCacheManagerProvider.provide(oop);
            case REDIS_CAFFEINE -> extRedisCaffeineCacheManagerProvider.provide(oop);
            default -> throw new IllegalArgumentException(LOG_PREFIX + "cannot support switch for enum [" + extCacheTypeEnum + "], \n\t curr ExtCacheableOop is -> " + oop);
        };
        return cacheManager;
    }
}